﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Common.Utilities;
using Common.Utilities.Dispatcher;

namespace Workflows.Components.ETL
{
    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class DataSamplerInstruction:InstructionBase
    {
        #region props
        private string _DataSourceName;
        /// <summary></summary>
        public string DataSourceName
        {
            get
            {
                return this._DataSourceName;
            }
            set
            {
                this._DataSourceName = value;
            }
        }

        private string _Sql;
        /// <summary></summary>
        public string Sql
        {
            get
            {
                return this._Sql;
            }
            set
            {
                this._Sql = value;
            }
        }

        private List<string> _TableNames;
        /// <summary>db.dbo.table</summary>
        public List<string> TableNames
        {
            get
            {
                return this._TableNames;
            }
            set
            {
                this._TableNames = value;
            }
        }

        private Dictionary<string,List<string>> _TableFields;
        /// <summary></summary>
        public Dictionary<string, List<string>> TableFields
        {
            get
            {
                return this._TableFields;
            }
            set
            {
                this._TableFields = value;
            }
        }

        private SamplingMethod _SamplingType;
        /// <summary></summary>
        public SamplingMethod SamplingType
        {
            get
            {
                return this._SamplingType;
            }
            set
            {
                this._SamplingType = value;
            }
        }

        private int _SampleSize;

        /// <summary></summary>
        public int SampleSize
        {
            get
            {
                return this._SampleSize;
            }
            set
            {
                this._SampleSize = value;
            }
        }

        private string _SampleByField;
        /// <summary>
        /// when empty, sample by all records
        /// </summary>
        public string SampleByField
        {
            get
            {
                return this._SampleByField;
            }
            set
            {
                this._SampleByField = value;
            }
        }

        private string _AggregateField;
        /// <summary></summary>
        public string AggregateField
        {
            get
            {
                return this._AggregateField;
            }
            set
            {
                this._AggregateField = value;
            }
        }

        private Dictionary<string,string> _FieldFilters;
        /// <summary></summary>
        public Dictionary<string,string> FieldFilters
        {
            get
            {
                return this._FieldFilters;
            }
            set
            {
                this._FieldFilters = value;
            }
        }

        private Dictionary<string,Dictionary<object,int>> _FieldDistributions;
        /// <summary>
        /// field name -> value distribution (field value -> percentile)
        /// when value distribution is null, use distribution in source table 
        /// </summary>
        public Dictionary<string,Dictionary<object,int>> FieldDistributions
        {
            get
            {
                return this._FieldDistributions;
            }
            set
            {
                this._FieldDistributions = value;
            }
        }

        private Dictionary<string,string> _FieldsAlias;
        /// <summary></summary>
        public Dictionary<string,string> FieldsAlias
        {
            get
            {
                return this._FieldsAlias;
            }
            set
            {
                this._FieldsAlias = value;
            }
        }

        private Dictionary<string,AggregateFunction> _FieldAggregateFunctions;
        /// <summary></summary>
        public Dictionary<string,AggregateFunction> FieldAggregateFunctions
        {
            get
            {
                return this._FieldAggregateFunctions;
            }
            set
            {
                this._FieldAggregateFunctions = value;
            }
        }

        private Dictionary<string,string> _FieldCustomFunctions;
        /// <summary></summary>
        public Dictionary<string,string> FieldCustomFunctions
        {
            get
            {
                return this._FieldCustomFunctions;
            }
            set
            {
                this._FieldCustomFunctions = value;
            }
        }

        private string _OutputFilePath;
        /// <summary></summary>
        public string OutputFilePath
        {
            get
            {
                return this._OutputFilePath;
            }
            set
            {
                this._OutputFilePath = value;
            }
        }
        #endregion

        #region ctor 
        /// <summary>
        /// 
        /// </summary>
        public DataSamplerInstruction():base(Guid.Empty, Guid.Empty)
        {
            this._DataSourceName = string.Empty;
            this._Sql = string.Empty;
            this._TableNames=new List<string>();
            this._TableFields = new Dictionary<string, List<string>>();
            this._SamplingType = SamplingMethod.Count;
            this._SampleSize = 500;
            this._SampleByField = string.Empty;
            this._AggregateField = string.Empty;
            this._FieldFilters=new Dictionary<string, string>();
            this._FieldDistributions=new Dictionary<string, Dictionary<object, int>>();
            this._FieldsAlias=new Dictionary<string, string>();
            this._FieldAggregateFunctions = new Dictionary<string, AggregateFunction>();
            this._FieldCustomFunctions = new Dictionary<string, string>();
            this._OutputFilePath = string.Empty;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="componentID"></param>
        /// <param name="jobTicketID"></param>
        /// <param name="dataSrc"></param>
        /// <param name="sql"></param>
        /// <param name="tblNames"></param>
        /// <param name="tblFields"></param>
        /// <param name="sampleMethod"></param>
        /// <param name="sampleSize"></param>
        /// <param name="sampleByField"></param>
        /// <param name="aggregateField"></param>
        /// <param name="filters"></param>
        /// <param name="distributions"></param>
        /// <param name="fieldAggregateFunctions"></param>
        /// <param name="fieldCustomFunctions"></param>
        /// <param name="outputFilePath"></param>
        /// <param name="fieldsAlias"></param>
        public DataSamplerInstruction(
            Guid componentID, Guid jobTicketID,
            string dataSrc, string sql,
            List<string> tblNames, Dictionary<string, List<string>> tblFields,
            SamplingMethod sampleMethod, int sampleSize, 
            string sampleByField, string aggregateField,
            Dictionary<string,string> filters, 
            Dictionary<string,Dictionary<object,int>> distributions,
            Dictionary<string,string> fieldsAlias,
            Dictionary<string, AggregateFunction> fieldAggregateFunctions,
            Dictionary<string,string> fieldCustomFunctions,
            string outputFilePath) 
            : base(componentID, jobTicketID)
        {
            this._DataSourceName = dataSrc;
            this._Sql = sql;
            this._TableNames = tblNames;
            this._TableFields = tblFields;
            this._SamplingType = sampleMethod;
            this._SampleSize = sampleSize;
            this._SampleByField = sampleByField;
            this._AggregateField = aggregateField;
            this._SampleByField = sampleByField;
            this._FieldFilters = filters;
            this._FieldDistributions = distributions;
            this._FieldsAlias = fieldsAlias;
            this._FieldAggregateFunctions = fieldAggregateFunctions;
            this._FieldCustomFunctions = fieldCustomFunctions;
            this._OutputFilePath = outputFilePath;
        }
        #endregion

        #region Overrides of InstructionBase

        /// <summary>
        /// serialize settings to xml
        /// the function below is mainly for storing into database as serialized form instead of string
        /// </summary>
        public override XmlNode Serialize(ref XmlDocument xDoc, ref XmlNode parentNode)
        {
            XmlNode xNode = XmlDataUtil.AddElement(ref xDoc, parentNode, "Instruction");
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "Type", this.GetType().FullName);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "ComponentID", this.OwnerComponentID.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "WorkerID", this.OwnerWorkerInstanceID.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "SubmitTime", this.SubmitTime.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "StartTime", this.StartTime.ToString());

            XmlDataUtil.UpdateAttribute(ref xDoc, xNode,"DataSource",this.DataSourceName);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode,"Sql",this.Sql);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "SampleMethod",this.SamplingType.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "SampleSize",this.SampleSize.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "SampleByField",this.SampleByField);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode,"AggregateField", this.AggregateField);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode,"OutputFilePath",this.OutputFilePath);

            XmlNode tblNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "Tables");
            if (this.TableNames != null && this.TableNames.Count > 0)
            {
                foreach (string tblName in this.TableNames)
                {
                    XmlNode tblNode = XmlDataUtil.AddElement(ref xDoc, tblNodes, "Table");
                    XmlDataUtil.UpdateAttribute(ref xDoc, tblNode, "TableName",tblName);
                    if(this.TableFields !=null && this.TableFields.ContainsKey(tblName))
                    {
                        List<string> fieldNames = this.TableFields[tblName];
                        foreach(string fieldName in fieldNames)
                        {
                            XmlNode fieldNode = XmlDataUtil.AddElement(ref xDoc, tblNode, "Field");
                            XmlDataUtil.UpdateAttribute(ref xDoc, fieldNode, "FieldName", fieldName);
                        }
                    }
                }
            }
            XmlNode filterNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "FieldFilters");
            if(this.FieldFilters !=null && this.FieldFilters.Count>0)
            {
                foreach(string fieldName in this.FieldFilters.Keys)
                {
                    XmlNode filterNode = XmlDataUtil.AddElement(ref xDoc, filterNodes, "FieldFilter");
                    XmlDataUtil.UpdateAttribute(ref xDoc, filterNode, "FieldName",fieldName);
                    XmlDataUtil.UpdateAttribute(ref xDoc, filterNode, "Sql", this.FieldFilters[fieldName]);
                }
            }
            XmlNode distNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "Distributions");
            if(this.FieldDistributions !=null && this.FieldDistributions.Count>0)
            {
                foreach(string fieldName in this.FieldDistributions.Keys)
                {
                    XmlNode distNode = XmlDataUtil.AddElement(ref xDoc, distNodes, "Distribution");
                    XmlDataUtil.UpdateAttribute(ref xDoc, distNode,"FieldName",fieldName);
                    foreach(object value in this.FieldDistributions[fieldName].Keys)
                    {
                        XmlNode valueNode = XmlDataUtil.AddElement(ref xDoc, distNode, "Value");
                        XmlDataUtil.UpdateAttribute(ref xDoc, valueNode, "Value", value.ToString());
                        XmlDataUtil.UpdateAttribute(ref xDoc, valueNode, "Percent", this.FieldDistributions[fieldName][value].ToString());
                    }
                }
            }
            XmlNode fieldAliasNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "FieldsAlias");
            if(this.FieldsAlias !=null && this.FieldsAlias.Count>0)
            {
                foreach(string fieldName in this.FieldsAlias.Keys)
                {
                    XmlNode fieldAliasNode = XmlDataUtil.AddElement(ref xDoc, fieldAliasNodes, "FieldAlias");
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldAliasNode, "FieldName",fieldName);
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldAliasNode, "Alias", this.FieldsAlias[fieldName]);
                }
            }
            XmlNode fieldAggrNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "FieldAggregates");
            if(this.FieldAggregateFunctions !=null && this.FieldAggregateFunctions.Count>0)
            {
                foreach(string fieldName in this.FieldAggregateFunctions.Keys)
                {
                    XmlNode fieldAggrNode = XmlDataUtil.AddElement(ref xDoc, fieldAggrNodes, "FieldAggregate");
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldAggrNode, "FieldName", fieldName);
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldAggrNode, "Aggregate", this.FieldAggregateFunctions[fieldName].ToString());
                }
            }
            XmlNode fieldCustNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "FieldFunctions");
            if(this.FieldCustomFunctions !=null && this.FieldCustomFunctions.Count>0)
            {
                foreach(string fieldName in this.FieldCustomFunctions.Keys)
                {
                    XmlNode fieldCustNode = XmlDataUtil.AddElement(ref xDoc, fieldCustNodes, "FieldFunction");
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldCustNode, "FieldName", fieldName);
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldCustNode, "Function", this.FieldCustomFunctions[fieldName]);
                }
            }

            return xNode;
        }

        /// <summary>
        /// instantiated from xml
        /// "&lt;Instructions&gt;&lt;Instruction&gt;&lt;/Instruction&gt;&lt;/Instruction&gt;", must
        /// instantiate from first child of root node
        /// </summary>
        public override InstructionBase Instantiate(XmlNode xNode)
        {
            if (xNode.Attributes.GetNamedItem("Type").Value == this.GetType().FullName)
            {
                DataSamplerInstruction instruction = new DataSamplerInstruction();
                instruction.OwnerComponentID = new Guid(XmlDataUtil.GetAttributeValue(xNode, "ComponentID", Guid.NewGuid().ToString()));
                instruction.OwnerWorkerInstanceID = new Guid(XmlDataUtil.GetAttributeValue(xNode, "WorkerID", Guid.NewGuid().ToString()));
                instruction.SubmitTime = DateTime.Parse(XmlDataUtil.GetAttributeValue(xNode, "SubmitTime", TimeUtil.DbMinTime.ToString()));
                instruction.StartTime = DateTime.Parse(XmlDataUtil.GetAttributeValue(xNode, "StartTime", TimeUtil.DbMinTime.ToString()));

                instruction.DataSourceName = XmlDataUtil.GetAttributeValue(xNode, "DataSource", "");
                instruction.Sql = XmlDataUtil.GetAttributeValue(xNode, "Sql", "");
                instruction.SamplingType = (SamplingMethod) Enum.Parse(typeof (SamplingMethod),
                                                                       XmlDataUtil.GetAttributeValue(xNode,
                                                                                                     "SampleMethod",
                                                                                                     SamplingMethod.
                                                                                                         Count.ToString()));
                instruction.SampleSize = int.Parse(XmlDataUtil.GetAttributeValue(xNode, "SampleSize", "500"));
                instruction.SampleByField = XmlDataUtil.GetAttributeValue(xNode, "SampleByField", "");
                instruction.AggregateField = XmlDataUtil.GetAttributeValue(xNode, "AggregateField", "");
                instruction.OutputFilePath = XmlDataUtil.GetAttributeValue(xNode, "OutputFilePath", "");

                instruction.TableNames=new List<string>();
                instruction.TableFields=new Dictionary<string, List<string>>();
                XmlNodeList tblNodes = xNode.SelectNodes("Tables/Table");
                if(tblNodes !=null && tblNodes.Count>0)
                {
                    foreach(XmlNode tblNode in tblNodes)
                    {
                        string tblName = XmlDataUtil.GetAttributeValue(tblNode, "TableName", "");
                        instruction.TableNames.Add(tblName);
                        List<string> tblFields=new List<string>();
                        if(tblNode.ChildNodes !=null && tblNode.ChildNodes.Count>0)
                        {
                            foreach(XmlNode fieldNode in tblNode.ChildNodes)
                            {
                                string fieldName = XmlDataUtil.GetAttributeValue(fieldNode, "FieldName", "");
                                tblFields.Add(fieldName);
                            }
                        }
                        instruction.TableFields.Add(tblName, tblFields);
                    }
                }
                instruction.FieldFilters=new Dictionary<string, string>();
                XmlNodeList filterNodes = xNode.SelectNodes("FieldFilters/FieldFilter");
                if(filterNodes !=null && filterNodes.Count>0)
                {
                    foreach(XmlNode filterNode in filterNodes)
                    {
                        string fieldName = XmlDataUtil.GetAttributeValue(filterNode, "FieldName", "");
                        string sql = XmlDataUtil.GetAttributeValue(filterNode, "Sql", "");
                        instruction.FieldFilters.Add(fieldName, sql);
                    }
                }
                instruction.FieldDistributions=new Dictionary<string, Dictionary<object, int>>();
                XmlNodeList distNodes = xNode.SelectNodes("Distributions/Distribution");
                if(distNodes !=null && distNodes.Count>0)
                {
                    foreach(XmlNode distNode in distNodes)
                    {
                        string fieldName = XmlDataUtil.GetAttributeValue(distNode, "FieldName", "");
                        Dictionary<object,int> valueDistributions=new Dictionary<object, int>();
                        if(distNode.ChildNodes !=null && distNode.ChildNodes.Count>0)
                        {
                            foreach(XmlNode valNode in distNode.ChildNodes)
                            {
                                string objValue = XmlDataUtil.GetAttributeValue(valNode, "Value", "");
                                int percent = int.Parse(XmlDataUtil.GetAttributeValue(valNode, "Percent", "0"));
                                valueDistributions.Add(objValue,percent);
                            }
                        }
                        instruction.FieldDistributions.Add(fieldName, valueDistributions);
                    }
                }
                instruction.FieldsAlias=new Dictionary<string, string>();
                XmlNodeList aliasNodes = xNode.SelectNodes("FieldsAlias/FieldAlias");
                if(aliasNodes !=null && aliasNodes.Count>0)
                {
                    foreach(XmlNode aliasNode in aliasNodes)
                    {
                        string fieldName = XmlDataUtil.GetAttributeValue(aliasNode, "FieldName", "");
                        string alias = XmlDataUtil.GetAttributeValue(aliasNode, "Alias", "");
                        instruction.FieldsAlias.Add(fieldName, alias);
                    }
                }
                instruction.FieldAggregateFunctions=new Dictionary<string, AggregateFunction>();
                XmlNodeList aggrNodes = xNode.SelectNodes("FieldAggregates/FieldAggregate");
                if(aggrNodes !=null && aggrNodes.Count>0)
                {
                    foreach(XmlNode aggreNode in aggrNodes)
                    {
                        string fieldName = XmlDataUtil.GetAttributeValue(aggreNode, "FieldName", "");
                        AggregateFunction aggrFunc = (AggregateFunction) Enum.Parse(typeof (AggregateFunction),
                                                                                    XmlDataUtil.GetAttributeValue(
                                                                                        aggreNode, "Aggregate",
                                                                                        AggregateFunction.CountDistinct.
                                                                                            ToString()));
                        instruction.FieldAggregateFunctions.Add(fieldName, aggrFunc);
                    }
                }
                instruction.FieldCustomFunctions=new Dictionary<string, string>();
                XmlNodeList funcNodes = xNode.SelectNodes("FieldFunctions/FieldFunction");
                if(funcNodes !=null && funcNodes.Count>0)
                {
                    foreach(XmlNode funcNode in funcNodes)
                    {
                        string fieldName = XmlDataUtil.GetAttributeValue(funcNode, "FieldName", "");
                        string function = XmlDataUtil.GetAttributeValue(funcNode, "Function", "");
                        instruction.FieldCustomFunctions.Add(fieldName, function);
                    }
                }
                return instruction;
            }
            else
            {
                throw new Exception("Unable to instantiate component: invalid type specified in xml node");
            }
        }

        #endregion
    }
}
